package io.gitee.zhangbinhub.admin.conf

import cn.dev33.satoken.context.SaHolder
import cn.dev33.satoken.context.model.SaRequest
import cn.dev33.satoken.context.model.SaResponse
import cn.dev33.satoken.context.model.SaStorage
import cn.dev33.satoken.exception.BackResultException
import cn.dev33.satoken.exception.NotLoginException
import cn.dev33.satoken.`fun`.SaFunction
import cn.dev33.satoken.`fun`.strategy.SaCorsHandleFunction
import cn.dev33.satoken.oauth2.annotation.handler.SaCheckAccessTokenHandler
import cn.dev33.satoken.oauth2.annotation.handler.SaCheckClientIdSecretHandler
import cn.dev33.satoken.oauth2.annotation.handler.SaCheckClientTokenHandler
import cn.dev33.satoken.oauth2.config.SaOAuth2ServerConfig
import cn.dev33.satoken.oauth2.consts.GrantType
import cn.dev33.satoken.oauth2.consts.SaOAuth2Consts
import cn.dev33.satoken.oauth2.data.model.loader.SaClientModel
import cn.dev33.satoken.oauth2.strategy.SaOAuth2Strategy
import cn.dev33.satoken.oauth2.template.SaOAuth2Util
import cn.dev33.satoken.router.SaHttpMethod
import cn.dev33.satoken.router.SaRouter
import cn.dev33.satoken.solon.integration.SaTokenFilter
import cn.dev33.satoken.stp.StpUtil
import cn.dev33.satoken.strategy.SaAnnotationStrategy
import cn.dev33.satoken.strategy.SaStrategy
import io.gitee.zhangbinhub.acp.boot.conf.AcpCorsConfiguration
import io.gitee.zhangbinhub.acp.boot.http.HttpHeaders
import io.gitee.zhangbinhub.acp.core.common.CommonTools
import io.gitee.zhangbinhub.acp.core.common.log.LogFactory
import io.gitee.zhangbinhub.admin.api.ServerApi
import io.gitee.zhangbinhub.admin.authentication.UserPasswordGrantTypeHandler
import io.gitee.zhangbinhub.admin.constant.OauthConstant
import io.gitee.zhangbinhub.admin.constant.RestPrefix
import io.gitee.zhangbinhub.admin.service.ApplicationService
import io.gitee.zhangbinhub.admin.tools.TokenTools
import org.noear.solon.Solon
import org.noear.solon.annotation.Bean
import org.noear.solon.annotation.Configuration
import org.noear.solon.annotation.Inject

@Configuration
class AcpOauthServerAutoConfiguration(
    private val applicationService: ApplicationService,
    private val saOAuth2ServerConfig: SaOAuth2ServerConfig, userPasswordGrantTypeHandler: UserPasswordGrantTypeHandler,
    private val acpCorsConfiguration: AcpCorsConfiguration
) {
    private val log = LogFactory.getInstance(this.javaClass)

    init {
        SaOAuth2Consts.Api.authorize = "/inner/oauth/authorize"
        SaOAuth2Consts.Api.token = "/inner/oauth/token"
        SaOAuth2Consts.Api.refresh = "/inner/oauth/refresh"
        SaOAuth2Consts.Api.revoke = "/inner/oauth/revoke"
        SaOAuth2Consts.Api.client_token = "/inner/oauth/client_token"
        SaOAuth2Consts.Api.doLogin = "/inner/oauth/doLogin"
        SaOAuth2Consts.Api.doConfirm = "/inner/oauth/doConfirm"
        SaOAuth2Strategy.instance.registerGrantTypeHandler(userPasswordGrantTypeHandler)
    }

    fun loadClientInfo() {
        applicationService.getAppList().map { application ->
            SaClientModel()
                .setClientId(application.id)
                .setClientSecret(application.secret)
                .setClientTokenTimeout(application.accessTokenValiditySeconds.toLong()) // 单位秒
                .setAccessTokenTimeout(application.accessTokenValiditySeconds.toLong()) // 单位秒
                .setRefreshTokenTimeout(application.refreshTokenValiditySeconds.toLong()) // 单位秒
                .addContractScopes(*(application.scope ?: "").split(",").toTypedArray())
                .addAllowGrantTypes(
                    GrantType.client_credentials,
                    GrantType.refresh_token,
                    OauthConstant.granterUserPassword
                )
        }.associateBy { it.clientId }.apply {
            saOAuth2ServerConfig.setClients(this)
        }
    }

    @Bean
    fun saTokenInterceptor(@Inject acpAuthConfiguration: AcpAuthConfiguration): SaTokenFilter =
        SaTokenFilter().apply {
            val permitAll = permitAllPath(acpAuthConfiguration)
            permitAll.forEach { uri -> log.info("permitAll uri: $uri") }
            this.addInclude(ALL_PATH)
            this.setExcludeList(permitAll)
            this.setAuth {
                SaRouter.notMatch(SaHttpMethod.OPTIONS).match(ALL_PATH, SaFunction {
                    val accessToken = TokenTools.getAccessTokenModel()
                    val clientToken = TokenTools.getClientTokenModel()
                    if (accessToken != null) {
                        SaOAuth2Util.checkAccessToken(accessToken.accessToken)
                        try {
                            StpUtil.checkLogin()
                        } catch (e: NotLoginException) {
                            throw NotLoginException("", "没有权限，请联系系统管理员", "-2").setCode(11012)
                        }
                    }
                    if (clientToken != null) {
                        SaOAuth2Util.checkClientToken(clientToken.clientToken)
                    }
                    if (accessToken == null && clientToken == null) {
                        throw NotLoginException("", "没有权限，请联系系统管理员", "-2").setCode(11012)
                    }
                })
            }
            this.setBeforeAuth {
                SaHolder.getResponse()
                    // 是否可以在iframe显示视图： DENY=不可以 | SAMEORIGIN=同域下可以 | ALLOW-FROM uri=指定域名下可以
                    .setHeader("X-Frame-Options", "SAMEORIGIN")
                    // 是否启用浏览器默认XSS防护： 0=禁用 | 1=启用 | 1; mode=block 启用, 并在检查到XSS攻击时，停止渲染页面
                    .setHeader("X-XSS-Protection", "1; mode=block")
                    // 禁用浏览器内容嗅探
                    .setHeader("X-Content-Type-Options", "nosniff")
            }
        }.also {
            SaAnnotationStrategy.instance.registerAnnotationHandler(SaCheckAccessTokenHandler())
            SaAnnotationStrategy.instance.registerAnnotationHandler(SaCheckClientIdSecretHandler())
            SaAnnotationStrategy.instance.registerAnnotationHandler(SaCheckClientTokenHandler())
            // 跨域处理
            if (acpCorsConfiguration.enabled) {
                SaStrategy.instance.corsHandle = saCorsHandleFunction(acpCorsConfiguration)
            }
        }

    private fun saCorsHandleFunction(acpCorsConfiguration: AcpCorsConfiguration): SaCorsHandleFunction =
        SaCorsHandleFunction { request: SaRequest, response: SaResponse, storage: SaStorage? ->
            val origin = request.getHeader(HttpHeaders.ORIGIN)
            if (CommonTools.isNullStr(origin)) {
                return@SaCorsHandleFunction
            }
            response.setHeader(HttpHeaders.ACCESS_CONTROL_MAX_AGE, acpCorsConfiguration.maxAge.toString())
            if (acpCorsConfiguration.allowCredentials) {
                response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true")
            }
            if (acpCorsConfiguration.allowedMethods.isEmpty()) {
                response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, "*")
            } else {
                response.setHeader(
                    HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS,
                    acpCorsConfiguration.allowedMethods.joinToString(separator = ",")
                )
            }
            if (acpCorsConfiguration.allowedHeaders.isEmpty()) {
                response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, "*")
            } else {
                response.setHeader(
                    HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS,
                    acpCorsConfiguration.allowedHeaders.joinToString(separator = ",")
                )
            }
            if (acpCorsConfiguration.exposedHeaders.isEmpty()) {
                response.setHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "*")
            } else {
                response.setHeader(
                    HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS,
                    acpCorsConfiguration.exposedHeaders.joinToString(separator = ",")
                )
            }
            if (acpCorsConfiguration.allowedOrigins.contains("*") || acpCorsConfiguration.allowedOrigins.contains(
                    origin
                )
            ) {
                response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, origin)
            } else {
                throw BackResultException("Invalid CORS request")
            }
        }

    companion object {
        private const val ALL_PATH = "/**"
        /**
         * 放行安全检查的开放Path
         */
        fun permitAllPath(acpAuthConfiguration: AcpAuthConfiguration) =
            mutableListOf<String>().apply {
                this.add("${Solon.cfg().serverContextPath()}/error")
                this.add("${Solon.cfg().serverContextPath()}/favicon.ico")
                this.add("${Solon.cfg().serverContextPath()}/swagger/**")
                this.add("${Solon.cfg().serverContextPath()}/swagger-resources")
                this.add("${Solon.cfg().serverContextPath()}/v3/api-docs/**")
                this.add("${Solon.cfg().serverContextPath()}/*.html")
                this.add("${Solon.cfg().serverContextPath()}/webjars/**")
                this.add("/solon-admin/api/monitor/data")
                this.add("/healthz")
                this.add("${Solon.cfg().serverContextPath()}${SaOAuth2Consts.Api.authorize}")
                this.add("${Solon.cfg().serverContextPath()}${SaOAuth2Consts.Api.token}")
                this.add("${Solon.cfg().serverContextPath()}${SaOAuth2Consts.Api.refresh}")
                this.add("${Solon.cfg().serverContextPath()}${SaOAuth2Consts.Api.revoke}")
                this.add("${Solon.cfg().serverContextPath()}${SaOAuth2Consts.Api.client_token}")
                this.add("${Solon.cfg().serverContextPath()}${SaOAuth2Consts.Api.doLogin}")
                this.add("${Solon.cfg().serverContextPath()}${SaOAuth2Consts.Api.doConfirm}")
                acpAuthConfiguration.permitAllPath.forEach { path ->
                    this.add(
                        Solon.cfg().serverContextPath() + path
                    )
                }
                this.add("${Solon.cfg().serverContextPath()}${RestPrefix.Open}$ALL_PATH")
                this.add("${Solon.cfg().serverContextPath()}${ServerApi.basePath}${ServerApi.token}")
            }
    }
}